/* Makra
SHADOWS = 0, 1
SPECULAR = 0, 1
*/

#include "Deferred.fxh"

float4 g_PointLight_Color;
float g_PointLight_RangeSq;
float3 g_PointLight_Pos;

#if SHADOWS != 0
	float3 g_CascadeDepths;
	texture g_ShadowMapTexture0;
	texture g_ShadowMapTexture1;
	texture g_ShadowMapTexture2;
	float4x4 g_ShadowMapXform0;
	float4x4 g_ShadowMapXform1;
	float4x4 g_ShadowMapXform2;
	float3 g_ShadowParams; // x=min, y=a, z=b
	float2 g_ShadowMapSizes; // Size, 1/Size
	float g_ShadowFactor; // Used for spot and point light

	sampler ShadowMapSampler0 = sampler_state {
		Texture = (g_ShadowMapTexture0); BorderColor = 0xFFFFFF;
		AddressU = BORDER; AddressV = BORDER;
		MinFilter = POINT; MagFilter = POINT; MipFilter = NONE;
	};
	sampler ShadowMapSampler1 = sampler_state {
		Texture = (g_ShadowMapTexture1); BorderColor = 0xFFFFFF;
		AddressU = BORDER; AddressV = BORDER;
		MinFilter = POINT; MagFilter = POINT; MipFilter = NONE;
	};
	sampler ShadowMapSampler2 = sampler_state {
		Texture = (g_ShadowMapTexture2); BorderColor = 0xFFFFFF;
		AddressU = BORDER; AddressV = BORDER;
		MinFilter = POINT; MagFilter = POINT; MipFilter = NONE;
	};
#endif

float CalcSpecularBrightness(float3 DirToLight, GBUFFER_DATA Data)
{
	float3 ReflectedDir = reflect(-DirToLight, Data.Normal_View);
	float R = pow(ReflectedDir.z, Data.SpecularExponent) * Data.SpecularIntensity;
	return saturate(R);
}

#if SHADOWS != 0
	/*#define SHADOW_SAMPLE_COUNT 9
	const float2 ShadowSampleOffsets[SHADOW_SAMPLE_COUNT] = {
		float2(-1, -1),
		float2( 0, -1),
		float2( 1, -1),
		float2(-1,  0),
		float2( 0,  0),
		float2( 1,  0),
		float2(-1,  1),
		float2( 0,  1),
		float2( 1,  1),
	};*/

	// texCoord.xy = tex coord
	// texCoord.z = depth
	float CalcShadow(sampler smSampler, float3 texCoord)
	{
		/*float texelSize = g_ShadowMapSizes.y;
		float sum = 0;
		for (int i = 0; i < SHADOW_SAMPLE_COUNT; i++)
			sum += tex2D(smSampler, texCoord.xy + ShadowSampleOffsets[i] * texelSize).r < texCoord.z;
		return sum / SHADOW_SAMPLE_COUNT;*/
		
		
		// Percentage Closest Filtering
		float2 tmpTexCoord = texCoord.xy * g_ShadowMapSizes.x;
		float4 smTexCoord;
		smTexCoord.xy = tmpTexCoord;
		smTexCoord.zw = smTexCoord.xy + float2(1, 1);
		float2 lerpFactors = frac(tmpTexCoord);
		smTexCoord *= g_ShadowMapSizes.y;
		float4 smSamples;
		smSamples.x = tex2D(smSampler, smTexCoord.xy).r;
		smSamples.y = tex2D(smSampler, smTexCoord.zy).r;
		smSamples.z = tex2D(smSampler, smTexCoord.xw).r;
		smSamples.w = tex2D(smSampler, smTexCoord.zw).r;
		float4 cmpResults = smSamples < texCoord.zzzz;
		float2 lerpTmp = lerp(cmpResults.xz, cmpResults.yw, lerpFactors.x);
		return lerp(lerpTmp.x, lerpTmp.y, lerpFactors.y);
	}

	float CalcShadowMultiplier_Directional(GBUFFER_DATA Data)
	{
		float shadow;
		
		// Above shadow max dist
		if (Data.Pos_View.z > g_CascadeDepths.z)
			shadow = 0;
		// Cascade 2
		else if (Data.Pos_View.z > g_CascadeDepths.y)
			shadow = CalcShadow(ShadowMapSampler2, mul(float4(Data.Pos_View, 1), (float4x3)g_ShadowMapXform2));
		// Cascade 1
		else if (Data.Pos_View.z > g_CascadeDepths.x)
			shadow = CalcShadow(ShadowMapSampler1, mul(float4(Data.Pos_View, 1), (float4x3)g_ShadowMapXform1));
		// Cascade 0
		else
			shadow = CalcShadow(ShadowMapSampler0, mul(float4(Data.Pos_View, 1), (float4x3)g_ShadowMapXform0));
		
		shadow *= saturate(Data.Pos_View.z*g_ShadowParams.y + g_ShadowParams.z);
		return lerp(1, g_ShadowParams.x, shadow);
	}

	float CalcShadowMultiplier_Spot(GBUFFER_DATA Data)
	{
		float4 texCoords = mul(float4(Data.Pos_View, 1), g_ShadowMapXform0);
		texCoords.xyz /= texCoords.w;
		float shadow = CalcShadow(ShadowMapSampler0, texCoords.xyz);
		return lerp(1, g_ShadowFactor, shadow);
	}

	float g_CubeShadowEpsilon;
	float4x3 g_ViewInv;
	float CalcShadowMultiplier_Point(GBUFFER_DATA Data)
	{
		float3 vecFromLight_View = Data.Pos_View - g_PointLight_Pos;
		float3 vecFromLight_World = mul(vecFromLight_View, (float3x3)g_ViewInv);
		float distFromLightSq = dot(vecFromLight_World, vecFromLight_World);
		float3 shadowTexCoords = normalize(vecFromLight_World);
		float3 lerps = frac(shadowTexCoords * g_ShadowMapSizes.xxx);
		float4 samples;
		samples.x = texCUBE(ShadowMapSampler0, shadowTexCoords + float3(0, 0, 0)).r;
		samples.y = texCUBE(ShadowMapSampler0, shadowTexCoords + float3(g_ShadowMapSizes.y, g_ShadowMapSizes.y, 0)).r;
		samples.z = texCUBE(ShadowMapSampler0, shadowTexCoords + float3(g_ShadowMapSizes.y, 0, g_ShadowMapSizes.y)).r;
		samples.w = texCUBE(ShadowMapSampler0, shadowTexCoords + float3(0, g_ShadowMapSizes.y, g_ShadowMapSizes.y)).r;
		float4 comparedSamples = (distFromLightSq / g_PointLight_RangeSq) > (samples + g_CubeShadowEpsilon);
		float shadow = dot(comparedSamples, 0.25);
		return lerp(1, g_ShadowFactor, shadow);
	}
#endif

float3 g_DirLight_DirToLight;
float3 g_DirLight_Color;
void DirectionalPS(in float2 Pos : VPOS, in float3 EyeVec : TEXCOORD0, out float4 Out : COLOR0)
{
	GBUFFER_DATA Data;
	SampleGBuffers(Pos, EyeVec, Data);
	
	float N_dot_L = dot(Data.Normal_View, g_DirLight_DirToLight);
	float brightness = saturate(N_dot_L);

	#if SHADOWS != 0
		brightness *= CalcShadowMultiplier_Directional(Data);
	#endif

	#if SPECULAR != 0
		float specularBrightness = CalcSpecularBrightness(g_DirLight_DirToLight, Data);
	#else
		float specularBrightness = 0;
	#endif
	
	Out.rgb = (Data.Diffuse + specularBrightness) * brightness * g_DirLight_Color;
	Out.a = 1;
}
float3 g_GoochParams;
void DirectionalGoochPS(in float2 Pos : VPOS, in float3 EyeVec : TEXCOORD0, out float4 Out : COLOR0)
{
	GBUFFER_DATA Data;
	SampleGBuffers(Pos, EyeVec, Data);
	
	float N_dot_L = dot(Data.Normal_View, g_DirLight_DirToLight);
	float brightness = saturate(N_dot_L);

	#if SHADOWS != 0
		brightness *= CalcShadowMultiplier_Directional(Data);
	#endif

	#if SPECULAR != 0
		float specularBrightness = CalcSpecularBrightness(g_DirLight_DirToLight, Data) * brightness;
	#else
		float specularBrightness = 0;
	#endif
	
	float3 DarkToBrightColor = Data.Diffuse * (brightness*g_GoochParams.x+g_GoochParams.y);
	float3 CoolToWarmColor = lerp(float3(0,0,1), float3(1,1,0), N_dot_L*0.5+0.5);
	
	Out.rgb = (lerp(CoolToWarmColor, DarkToBrightColor, g_GoochParams.z) + specularBrightness) * g_DirLight_Color;
	Out.a = 1;
}

void PointPS(in float2 Pos : VPOS, in float3 EyeVec : TEXCOORD0, out float4 Out : COLOR0)
{
	GBUFFER_DATA Data;
	SampleGBuffers(Pos, EyeVec, Data);
	
	float3 VecToLight = g_PointLight_Pos - Data.Pos_View;
	float3 DirToLight = normalize(VecToLight);

	// Lambert's law
	float brightness = saturate(dot(Data.Normal_View, DirToLight));
	// Distance attenuation
	brightness *= saturate(1 - dot(VecToLight, VecToLight)/g_PointLight_RangeSq);
	// Optimization
	//clip(brightness - 0.000001); // TODO uncomment
	// Shadow mapping
	#if SHADOWS != 0
		brightness *= CalcShadowMultiplier_Point(Data);
	#endif
	// Specular calculation
	#if SPECULAR != 0
		float specularBrightness = CalcSpecularBrightness(DirToLight, Data);
	#else
		float specularBrightness = 0;
	#endif
	// Final color
	Out.rgb = (Data.Diffuse + specularBrightness) * g_PointLight_Color * brightness;
	Out.a = 1;
}

float4 g_SpotLight_Color;
float g_SpotLight_RangeSq;
float3 g_SpotLight_Pos; // View space
float3 g_SpotLight_Dir; // View space
// xy=ab, where angle_brightness_factor = a*v+b, where
// v = cos(angle between light direction and direction from light position to the point)
// z = angle falloff exponent
float3 g_SpotLight_AngleParams;
void SpotPS(in float2 Pos : VPOS, in float3 EyeVec : TEXCOORD0, out float4 Out : COLOR0)
{
	GBUFFER_DATA Data;
	SampleGBuffers(Pos, EyeVec, Data);
	
	float3 VecToLight = g_SpotLight_Pos - Data.Pos_View;
	float3 DirToLight = normalize(VecToLight);
	// Lambert's law
	float brightness = saturate(dot(Data.Normal_View, DirToLight));
	// Distance attenuation
	brightness *= saturate(1 - dot(VecToLight, VecToLight)/g_SpotLight_RangeSq);
	// Angle falloff
	brightness *= pow(saturate(
		g_SpotLight_AngleParams.x * dot(g_SpotLight_Dir, -DirToLight) + g_SpotLight_AngleParams.y ), g_SpotLight_AngleParams.z);
	// Optimization
	clip(brightness - 0.000001);
	// Shadow Mapping
	#if SHADOWS != 0
		brightness *= CalcShadowMultiplier_Spot(Data);
	#endif
	// Specular calculation
	#if SPECULAR != 0
		float specularBrightness = CalcSpecularBrightness(DirToLight, Data);
	#else
		float specularBrightness = 0;
	#endif
	// Final color
	Out.rgb = (Data.Diffuse + specularBrightness) * brightness * g_SpotLight_Color;
	Out.a = 1;
}

technique Directional {
	pass {
		AlphaBlendEnable = true;
		SrcBlend  = ONE;
		DestBlend = ONE;
		VertexShader = compile vs_3_0 MainVS();
		PixelShader  = compile ps_3_0 DirectionalPS();
	}
}

technique DirectionalGooch {
	pass {
		AlphaBlendEnable = true;
		SrcBlend  = ONE;
		DestBlend = ONE;
		VertexShader = compile vs_3_0 MainVS();
		PixelShader  = compile ps_3_0 DirectionalGoochPS();
	}
}

technique Point {
	pass {
		AlphaBlendEnable = true;
		SrcBlend  = ONE;
		DestBlend = ONE;
		VertexShader = compile vs_3_0 MainVS();
		PixelShader  = compile ps_3_0 PointPS();
	}
}

technique Spot {
	pass {
		AlphaBlendEnable = true;
		SrcBlend  = ONE;
		DestBlend = ONE;
		VertexShader = compile vs_3_0 MainVS();
		PixelShader  = compile ps_3_0 SpotPS();
	}
}

